﻿using System;
using System.Collections.Generic;
using UnityEngine;

namespace WentStateMachine
{
    [Serializable]
    public sealed class StateMachine : ScriptableObject
    {
        string _name;
        public string Name
        {
            get
            {
                return _name;
            }
            set
            {
                name = value;
                _nameHash = Helper.ScriptingStringToCRC32(value);
                _name = value;
            }
        }

        [SerializeField] int _nameHash;
        public int nameHash {
            get {
                return _nameHash;
            }
            set
            {
                _nameHash = value;
            }
        }

        [SerializeField] Transition[] anyStateTransition;
        public Transition[] AnyStateTransition
        {
            get
            {
                return anyStateTransition;
            }
            set
            {
                anyStateTransition = value;
            }
        }

        [SerializeField] Transition[] entryTransition;
        public Transition[] EntryTransition
        {
            get
            {
                return entryTransition;
            }
            set
            {
                entryTransition = value;
            }
        }

        [SerializeField] State[] states;
        public State[] States
        {
            get
            {
                return states;
            }
            set
            {
                states = value;
            }
        }

        [SerializeField] StateMachine[] childStateMachine;
        public StateMachine[] ChildStateMachine
        {
            get
            {
                return childStateMachine;
            }
            set
            {
                childStateMachine = value;
            }
        }


        [SerializeField] int _defaultState; //暂时不支持跨状态机的DefaultState
        public int DefaultState
        {
            get
            {
                return _defaultState;
            }

            set
            {
                _defaultState = value;
            }
        }

        public StateMachine parentStateMachine
        {
            get;
            set;
        }

        public Transition[] currentTransitions { get;set; }
        public State currentState { get; set; }
        private State destinationState { get; set; }
        private StateMachine destinationStateMachine { get; set; }
        private StateMachineController controller;
        private int layerIndex;

        public void Init(StateMachineController controller, int layerIndex, StateMachine parent = null)
        {
            this.controller = controller;
            this.layerIndex = layerIndex;

            if(states != null)
            {
                for(int i = 0; i < states.Length; i++)
                {
                    states[i] = ScriptableObject.Instantiate(states[i]);
                    states[i].Init(this);
                }
            }

            if(childStateMachine != null)
            {
                for (int i = 0; i < childStateMachine.Length; i++)
                {
                    childStateMachine[i] = ScriptableObject.Instantiate(childStateMachine[i]);
                    childStateMachine[i].Init(controller, layerIndex, this);
                }
            }

            if(parent != null)
            {
                parentStateMachine = parent;
            }
        }

        public void Entry()
        {
            controller.layers[layerIndex].stateMachine = this;
            currentState = null;
            currentTransitions = null;
            checkNoneConditionTransition(entryTransition);
            if (destinationState == null || destinationStateMachine == null)
                destinationState = States[DefaultState];
        }

        public void Entry(State destination)
        {
            controller.layers[layerIndex].stateMachine = this;
            currentState = null;
            currentTransitions = null;
            destinationState = destination;
        }

        public void Entry(StateMachine destination)
        {
            controller.layers[layerIndex].stateMachine = this;
            currentState = null;
            currentTransitions = null;
            destinationStateMachine = destination;
        }

        public void Play(int hash)
        {
            if (tryFindStateByHash(hash, out var result))
            {
                moveTo(result);
            } else {
                var root = rootStateMachine();
                if (root.tryFindStateByHash(hash, out result))
                {
                    moveTo(result);
                }
                else if (rootStateMachine().tryFindStateByHashInChildren(hash, out result, this))
                {
                    moveTo(result);
                }
            }
        }

        public void Back()
        {
            if (parentStateMachine != null)
                parentStateMachine.Back();
            else
            {
                Entry();
            }
        }

        public void OnUpdate()
        {
            if(destinationState != null || destinationStateMachine != null)
            {
                moveToDestination();
                return;
            }
            if (currentState)
            {
                currentState.OnUpdate(controller, layerIndex);
            }
        }
        public void Verify(Dictionary<int, Parameter> parameters)
        {
            if (destinationState != null || destinationStateMachine != null)
                return;

            if(currentState == null && currentTransitions == null)
            {
                entryVerify(parameters);
                return;
            }

            foreach (var transition in anyStateTransition)
            {
                bool pass = false;
                if (transition.conditions != null)
                {
                    foreach (var condition in transition.conditions)
                    {
                        if (!verify(condition, parameters[condition.parameterHash]))
                        {
                            pass = false;
                            break;
                        }
                        pass = true;
                    }
                }
                else
                    pass = true;
                
                if (pass)
                {
                    doTransition(transition);
                    return;
                }
            }

            foreach (var transition in currentTransitions)
            {
                bool pass = false;
                if (transition.conditions != null)
                {
                    foreach (var condition in transition.conditions)
                    {
                        if (!verify(condition, parameters[condition.parameterHash]))
                        {
                            pass = false;
                            break;
                        }
                        pass = true;
                    }
                }
                else
                    pass = true;
                
                if (pass)
                {
                    doTransition(transition);
                    return;
                }
            }
        }

        private State findStateByIndex(int[] index)
        {
            StateMachine root = this;
            while (root.parentStateMachine != null)
                root = root.parentStateMachine;

            for(int i = 0; i < index.Length; i++)
            {
                if(i == index.Length - 1)
                {
                    return root.States[index[i]];
                }
                else
                {
                    root = root.ChildStateMachine[index[i]];
                }
            }
            return null;
        }

        private StateMachine findStateMachineByIndex(int[] index)
        {
            if (index == null)
                return null;
            StateMachine root = this;
            while (root.parentStateMachine != null)
                root = root.parentStateMachine;

            if (index.Length == 1 && index[0] == -1)
                return root;

            for (int i = 0; i < index.Length; i++)
            {
                if (i == index.Length - 1)
                {
                    return root.ChildStateMachine[index[i]];
                }
                else
                {
                    root = root.ChildStateMachine[index[i]];
                }
            }
            return root;
        }

        private bool verify(Condition condition, Parameter param)
        {
            switch (condition.mode)
            {
                case ConditionMode.If: 
                    {
                        return param.defaultBool;
                    }
                case ConditionMode.IfNot:
                    {
                        return !param.defaultBool;
                    }
                case ConditionMode.Equals:
                    {
                        return param.Equal(condition.threshold);
                    }
                case ConditionMode.NotEqual:
                    {
                        return param.NotEqual(condition.threshold);
                    }
                case ConditionMode.Less:
                    {
                        return param.Less(condition.threshold);
                    }
                case ConditionMode.Greater:
                    {
                        return param.Greater(condition.threshold);
                    }
            }
            return false;
        }

        private void entryVerify(Dictionary<int, Parameter> parameters)
        {
            foreach (var transition in anyStateTransition)
            {
                bool pass = false;
                if (transition.conditions != null)
                {
                    foreach (var condition in transition.conditions)
                    {
                        if (!verify(condition, parameters[condition.parameterHash]))
                        {
                            pass = false;
                            break;
                        }
                        pass = true;
                    }
                }
                else
                    pass = true;
                
                if (pass)
                {
                    doTransition(transition);
                    return;
                }
            }

            foreach (var transition in entryTransition)
            {
                bool pass = false;
                if (transition.conditions != null)
                {
                    foreach (var condition in transition.conditions)
                    {
                        if (!verify(condition, parameters[condition.parameterHash]))
                        {
                            pass = false;
                            break;
                        }
                        pass = true;
                    }
                }
                else
                    pass = true;
                
                if (pass)
                {
                    doTransition(transition);
                    return;
                }
            }

            moveTo(States[DefaultState]);
        }

        private void doTransition(Transition transition)
        {
            if(transition.destinationState.Length == 0 && transition.destinationStateMachine.Length == 0)
            {
                if(parentStateMachine != null)
                {
                    Back();
                }
                else
                {
                    Entry();
                }
                return;
            }

            if(transition.destinationStateMachine.Length > 0)
            {
                destinationStateMachine = findStateMachineByIndex(transition.destinationStateMachine);
                return;
            }

            if(transition.destinationState.Length > 0)
            {
                destinationState = findStateByIndex(transition.destinationState);
            }
        }

        private void moveToDestination()
        {
            if(destinationStateMachine != null)
            {
                currentState.Exit(controller, layerIndex);
                currentState = null;
                destinationStateMachine.Entry();

                destinationState = null;
                destinationStateMachine = null;
            }
            else if(destinationState != null)
            {
                moveTo(destinationState);

                destinationState = null;
                destinationStateMachine = null;
            }
        }

        private void moveToOtherStateMachine(State state)
        {
            if (currentState)
            {
                currentState.Exit(controller, layerIndex);
            }
            state.Owner.Entry(state);
        }

        private void moveToCurrentStateMachine(State state)
        {
            if (currentState)
            {
                currentState.Exit(controller, layerIndex);
            }
            currentState = state;
            currentState.Enter(controller, layerIndex);
            currentTransitions = currentState.Transitions ?? new Transition[0];
            checkNoneConditionTransition(currentTransitions);
        }

        private void checkNoneConditionTransition(Transition[] transitions)
        {
            if (transitions == null || transitions.Length == 0)
                return;

            foreach(var transition in transitions)
            {
                if(transition.conditions == null || transition.conditions.Length == 0)
                {
                    if(transition.destinationState.Length > 0 || transition.destinationStateMachine.Length > 0)
                    {
                        destinationState = findStateByIndex(transition.destinationState);
                        destinationStateMachine = findStateMachineByIndex(transition.destinationStateMachine);
                        return;
                    }
                }
            }
        }

        private void moveTo(State state)
        {
            Logger.Log($"move to {state.name}");
            if (state.Owner == this)
                moveToCurrentStateMachine(state);
            else
                moveToOtherStateMachine(state);
        }

        private StateMachine rootStateMachine()
        {
            if (parentStateMachine == null)
                return this;
            else
                return parentStateMachine.rootStateMachine();
        }

        private bool tryFindStateByHash(int hash,out State result)
        {
            foreach(var state in states)
            {
                if (state.IsHash(hash))
                {
                    result = state;
                    return true;
                }
            }
            result = null;
            return false;
        }

        private bool tryFindStateByHashInChildren(int hash, out State result, StateMachine toSkip = null)
        {
            foreach (var stateMachine in childStateMachine)
            {
                if (stateMachine == toSkip)
                    continue;
                if (stateMachine.tryFindStateByHash(hash, out var childResult))
                {
                    result = childResult;
                    return true;
                }
            }

            foreach (var stateMachine in childStateMachine)
            {
                if (stateMachine == toSkip)
                    continue;
                if (stateMachine.tryFindStateByHashInChildren(hash, out var childResult, toSkip))
                {
                    result = childResult;
                    return true;
                }
            }

            result = null;
            return false;
        }
    }
}
